<!doctype html>
<html lang="en">

  <head>
    <meta charset="utf-8">

    <title>使用Vue.js开发在线 H5 编辑器</title>

    <meta name="description" content="使用Vue.js开发在线 H5 编辑器">
    <meta name="author" content="yangjunjun">

    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">

    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">

    <link rel="stylesheet" href="css/reveal.css">
    <link rel="stylesheet" href="css/theme/black.css" id="theme">

    <!-- Theme used for syntax highlighting of code -->
    <link rel="stylesheet" href="lib/css/zenburn.css">

    <!-- Printing and PDF exports -->
    <script>
      var link = document.createElement( 'link' );
      link.rel = 'stylesheet';
      link.type = 'text/css';
      link.href = window.location.search.match( /print-pdf/gi ) ? 'css/print/pdf.css' : 'css/print/paper.css';
      document.getElementsByTagName( 'head' )[0].appendChild( link );
    </script>

    <!--[if lt IE 9]>
    <script src="lib/js/html5shiv.js"></script>
    <![endif]-->
    <style>
      .reveal section img {
        margin: 0;
        border: none;
        background: none;
      }
      .reveal table th, .reveal table td {
        text-align: left;
        padding: 0.2em 0.5em 0.2em 0.5em;
        border-bottom: 1px solid;
        border-right: 1px solid;
        border-left: 1px solid;
      }      
    </style>
  </head>

  <body>

    <div class="reveal">

      <div class="slides">

        <!-- start -->
        <section>
          <h3>使用Vue.js开发在线 H5 编辑器</h3>
          <p>
            <small>by <a href="https://github.com/yangjunjun">杨俊业</a></small>
          </p>
        </section>

        <!-- toc -->
        <section>
          <h2>主要内容</h2>
          <ul>
            <li>开发思路</li>
            <li>开发过程</li>
            <li>开发总结</li>
          </ul>
        </section>

        <!-- h5 编辑的开发思路 -->
        <section>
          <section>
            <h2>开发思路</h2>         
          </section>
          
          <!-- h5 store -->
<!--           <section>
            <img src="img/h5-store.png" alt="">
          </section> -->

          <!-- h5 具体一个 -->
<!--           <section>
            <img src="img/h5-sample.png" alt="">
            <aside class="notes">
              我们看看都有什么，
              1. 有很多页面
              2. 页面有很多元素（文字，图片，按钮，表单）
              3. 还有动画
            </aside>
          </section> -->

<!--           <section>
            <a target="_blank" href="http://h5.baidu.com/store/a5094e7b-6388-f5f8-6d57-62635961edd2">
              <img src="img/lizi.jpg" title="一个例子">
            </a>
          </section> -->

<!--           <section>
            <h2>分析</h2>
            <ol>
              <li>有很多页面</li>
              <li>页面有很多元素</li>
              <li>元素有很多类型（文字，图片，按钮，表单）</li>
            </ol>
          </section> -->
          
          <section>
            <!-- <p>H5 编辑器</p> -->
            <img src="img/h5-editor.png" alt="">
          </section>

          <section>
            <h2>一个例子</h2>
            <img src="img/qr-code.png" alt="">
          </section> 

          <section>
            <h2>分析</h2>
            <table>
              <tbody>
                <tr>
                  <td>有很多页面</td>
                  <td>→</td>
                  <td>数组</td>
                </tr>
                <tr>
                  <td>页面有很多元素</td>
                  <td>→</td>
                  <td>数组的内容</td>
                </tr>
                <tr>
                  <td>元素有很多类型</td>
                  <td>→</td>
                  <td>不同的对象</td>
                </tr>
              </tbody>
            </table>
          </section>
          
<section>
            <h2>生成 json <a href="http://jsoneditoronline.org/?id=18fa4bc395e5878c060385662fe4d88f">在线查看</a></h2>

            <pre><code>{
  title: "我的h5作品", 
  description: "我的h5作品, 有没有觉得很高大上啊~快来膜拜我！", 
  icon: "/static/img/icon.png", 
  pages: [
    {
      style: {
        backgroundColor: "#50E3C2", 
        image: null
      }, 
      comps: [
        {
          type: "hy-text", 
          text: "wdshare", 
          active: false, 
          isShowView: true, 
          isShowEdit: true, 
          isShowAnimate: false, 
          animate: {
            name: "none", 
            duration: 1, 
            delay: 0
          }, 
          position: {
            left: 59, 
            top: 73, 
            width: 198, 
            height: 42, 
            transform: 0
          }, 
          style: {
            fontFamily: "Microsoft Yahei", 
            fontSize: "26px", 
            color: "#000000", 
            fontWeight: "bold", 
            fontStyle: "normal", 
            textDecoration: "normal", 
            textAlign: "center", 
            lineHeight: 1.5
          }
        }, 
        {
          type: "hy-image", 
          active: true, 
          isShowView: true, 
          isShowEdit: true, 
          imgSrc: "\\static\\upload\\wdshare-logo.png", 
          isShowAnimate: false, 
          animate: {
            name: "none", 
            duration: 1, 
            delay: 0
          }, 
          zIndex: 0, 
          position: {
            left: 30, 
            top: 155, 
            width: 248, 
            height: 81, 
            transform: 0
          }, 
          style: {
            opacity: 1, 
            borderRadius: 0, 
            boxShadow: 0
          }
        }
      ]
    }, 
    {
      style: {
        backgroundColor: "#8B572A", 
        image: null
      }, 
      comps: [
        {
          type: "hy-text", 
          text: "第二页", 
          active: true, 
          isShowView: true, 
          isShowEdit: true, 
          isShowAnimate: false, 
          animate: {
            name: "none", 
            duration: 1, 
            delay: 0
          }, 
          position: {
            left: 49, 
            top: 195, 
            width: 206, 
            height: 55, 
            transform: 0
          }, 
          style: {
            fontFamily: "Microsoft Yahei", 
            fontSize: "30px", 
            color: "#FFFFFF", 
            fontWeight: "bold", 
            fontStyle: "normal", 
            textDecoration: "normal", 
            textAlign: "center", 
            lineHeight: 1.5
          }
        }
      ]
    }
  ]
}</code></pre>
          </section>


        <section>
          <h2>总结</h2>
          <ul>
            <li class="fragment">编辑JSON </li>
            <li class="fragment">编辑过程可视化 </li>            
          </ul>
          <!-- <img class="fragment" src="img/result.png" alt=""> -->
        </section>

        </section>

        <!-- 了解 Vue 在其中的作用, 具体开发过程 -->
        <section>
          
          <section>
            <h2>开发过程</h2>
            <aside class="notes">
              有了思路，接下来就是开发了，使用什么技术好呢，jQuery + 插件的形式，还是
              angualr, react ? 由于之前我刚刚学习了 vue.js ，我就想到了学习vue.js的一个官方的demo. 
            </aside>
          </section>
          
          <!-- vue todoMVC demo -->
          <section>
            <p>todoMVC demo</p>
            <a target="_blank" href="https://fiddle.jshell.net/yangjunjun/0tv522wz/2/show/light/">
              <img src="img/vue-logo.png"  style="background:none;">
            </a>
            <aside class="notes">
这个 todoMVC demo 的实质也是在编辑一个 json 实质, 这正是我们想要的，
再看一下代码，也很简单，说明我们使用 vue 来开发是可以的，可能还非常方面
            </aside>
          </section>



          
          <!-- 初始化  -->
          <section>
            <h2>初始化data</h2>
            <pre><code>new Vue({
  data: {
    slide: {
      title: '我的h5作品',
      description: "我的h5作品, 有没有觉得很高大上啊~快来膜拜我！",
      icon: '/static/img/icon.png',
      pages: [{
        style: {
          backgroundColor: '#ffffff',
          image: null
        },
        comps: []
      }]
    }
  }
})</code></pre>
          </section>

<section>
  <h2>增加页面</h2>
  <pre><code>// 新增页面
addPage: function() {
  this.pages.push({
    style: {
      backgroundColor: '#ffffff',
      image: null
    },
    comps: []
  });
  // 设置新增页面为激活状态
  this.activePageIndex = this.pages.length - 1;
  // 触发滚动条
  this.activeIscroll();
}
</code></pre>  
</section>

<section>
  <h2>删除页面</h2>
  <pre><code>// 删除页面
removePage: function(index) {
  // 如果只有一个页面，禁止删除
  if (this.pages.length <= 1) return;
  this.pages.splice(index, 1);
  // 触发滚动条
  this.activeIscroll();
}

</code></pre>  
</section>

<section>
  <h2>复制页面</h2>
  <pre><code>// 复制页面
copyPage: function(index) {
  // 深度复制
  var newPage = $.extend(true, {}, this.pages[index]);
  // 插入到需要复制的下一页
  this.pages.splice(index + 1, 0, newPage);
  this.activeIscroll();
}
</code></pre>  
</section>

<!-- 页面添加元素 -->

<!-- 添加文本元素 -->
<section>
  <h2>添加文本</h2>
  <pre><code>addText: function(){
  this.currentPage.comps.push({
    type: 'hy-text',
    text: '请输入文本',
    active: false,
    animate:{
      name: "none",
      duration: 1,
      delay: 0
    },
    position:{
      left: 60,
      top: 200,
      width: 200,
      height: 24,
      transform: 0
    },      
    style:{
      fontFamily: 'Microsoft Yahei',
      fontSize: '16px',
      color: '#000000',
      fontWeight: 'normal',
      fontStyle: 'normal',
      textDecoration: 'normal',
      textAlign: 'center',
      lineHeight: 1.5
    }
  });
  var len = this.currentPage.comps.length;
  this.currentComp = this.currentPage.comps[len -1 ];
}</code></pre>
</section>

<!-- 编辑文本元素 -->
<section>
  <h2>编辑文本元素</h2>
  <pre><code><template>
   <color-pick :color.sync="comp.style.color"></color-pick>
   <select v-model="fontSize">
     <option v-for="n in arrFontSize" :value="n">{{n}}</option>
   </select>   
</template>
<script>
  props: {
    comp: {
      type: Object,
      required: true
    }
  },
  computed: {
    fontSize:{
      get: function(){
        return this.comp.style.fontSize.replace('px','');
      },
      set: function(newVal){
        this.comp.style.fontSize = newVal + 'px';
      }
    }
  },
  components: {
    colorPick: require('../plugin/colorPick.vue')
  }    
</script></code></pre>
</section>

<!-- h5 展示 -->

<!-- <section>
  <h5>JSON → H5 页面</h5>
  <img src="img/json-h5.png" alt="">
</section> -->

<section>
  <h2>页面显示</h2>
  <pre><code><template>
<ul class="page-wrap">
  <!-- 页面循环  -->
  <li v-for="(index, page) in pages" track-by="$index"
      @click.stop="activePage(index)"
      :class="{active: index == activePageIndex}">
    <!-- 页面元素循环 -->
    <div class="page" :style="page.style">
      <component
        v-for="comp in page.comps" track-by="$index"
        :is="comp.type" 
        :comp="comp"
        >
      </component>
    </div>
  </li>
</ul>
</template></code></pre>
<aside class="notes">
  这里面有两个循环，一个是页面循环显示，一个是页面的元素循环显示，
  用到了 vue 的动态组件技术
</aside>
</section>

<!-- 图片显示 -->
<section>
  <h2>“图片”元素显示</h2>
  <pre><code><template>
  <div class="hy-comp" :style="{zIndex: comp.zIndex}">
    <drag
      :active="comp.active"
      :enable="dragEnable"
      :top.sync="comp.position.top"
      :left.sync="comp.position.left"
      :width.sync="comp.position.width"
      :height.sync="comp.position.height"
      :transform.sync="comp.position.transform">
      <div class="inner" 
        :class="{'animated': isAnimated}" 
        :style="[fixStyle, animateStyle]">
        <img :src="comp.imgSrc | realImgSrc">
      </div>
    </drag>
  </div>
</template>
<script>
export default {
  props: {
    comp: {
      type: Object,
      required: true
    }    
  },
  components: {
    drag: require('../plugin/drag.vue')
  }
}
</script></code></pre>
</section>
        </section>




        <!-- 响应式 -->
<!--         <section>
          <h2>vue的特性 - 响应式</h2>
          <p>
            数据改变，视图随之改变
          </p>
        </section>
 -->
        <!-- 组件化 -->
<!--         <section>
          <h2>vue的特性 - 组件化</h2>
          <ul>
            <li>功能细分 (header-zone， hy-text， hy-image> )</li>
            <li>代码复用 ( drag, dialog )</li>            
            <li>易于升级、维护</li>            
          </ul>
        </section> -->
        
        <!-- 开发过程中遇到的问题 -->
<!--         <section>
          <section>
            <h2>遇到的问题</h2>
          </section>

          <section>
            <h2>数据不响应</h2>
            <p>改变了数据，但是视图没有随之改变</p>
          </section>

          <section>
            <h2>原因</h2>
            <ul>
              <li>数据并没有改变</li>
              <li>改变的数据不是正确数据</li>
              <li>视图没有绑定正确的数据</li>
            </ul>
          </section>

          <section>
            <h2>响应混乱</h2>
            <p>改变了一个数据，但是一些无关的视图也随之改变</p>
          </section>

          <section>
            <h2>原因</h2>
            <ul>
              <li>数据复制的时候，没有深度复制，导致多个的视图公用一个对象</li>
              <li>方案：复制数据的时候要深度复制, 比如使用:<br><code>$.extend(true, {}, this.pages[index])</code></li>              
            </ul>
          </section>

         <section>
           <h2>调试</h2>
           <ul>
             <li>使用 chrome 的开发者工具</li>
             <li>使用 vue Devtools (chrome 的一个扩展)</li>
             <li>debugger 语句，打断点</li>
           </ul>
         </section>

        </section> -->
        
        <!-- jquery插件 和 vue 配合使用 -->
        <section>

          <section>
            <h2>插件使用</h2>
          </section>
          
<!--           <section>
            <h2>使用方式</h2>
            <ul>
              <li>直接在组件中使用</li>
              <li>封装成一个vue 组件</li>
            </ul>
          </section> -->

          <!-- 以 iScroll 为例-->
          <section>
            <h2>iScroll 插件</h2>
            <pre><code>// 触发滚动条
activeIscroll: function(){
  this.$nextTick(function(){
    if(this.iscroll) {
      this.iscroll.refresh();
    }
    else {
      this.iscroll = new IScroll(this.$els.tabPane, { 
        mouseWheel: true,
        scrollbars: true
      });
    }          
  });
},
addPage: function(){
  this.pages.push(defautPageSetting());
  this.activePageIndex = this.pages.length - 1;
  this.activeIscroll();
}</code></pre>
          </section>

          <!-- 以 drag 为例-->
          <section>
            <p>jQuery UI中的resize, drag 插件</p>
            <pre><code>ready: function(){
  var _this = this;
  if( this.enable ){
    $( this.$el ).resizable({
      handles: _this.handles,
      resize: function(evt, ui){
        _this.width = ui.size.width;
        _this.height = ui.size.height;
        _this.top = Number(ui.position.top.toFixed(2));
        _this.left = Number(ui.position.left.toFixed(2));
      }
    }).draggable({
      drag: function(evt, ui){
        _this.top = Number(ui.position.top.toFixed(2));
        _this.left = Number(ui.position.left.toFixed(2));
      }
    });
  }
}</code></pre>
          </section>

<!--         <section>
          <h2> 遇到的问题 </h2>
          <p class="fragment">插件不起作用</p>
        </section> -->

<!--         <section>
          <h2>原因：</h2>
          <ol>
            <li>没有找到正确的 dom 节点</li>
            <li>没有找到正确的 this </li>
            <li>在Dom节点没有插入Dom 的时候调用</li>
          </ol>
        </section> -->

<!--         <section>
          <h2>没有找到正确的 data</h2>
            <pre><code>ready: function(){
  var _this = this;
  if( this.enable ){
    $( this.$el ).resizable({
      handles: _this.handles,
      resize: function(evt, ui){
        _this.width = ui.size.width;
        _this.height = ui.size.height;
        _this.top = Number(ui.position.top.toFixed(2));
        _this.left = Number(ui.position.left.toFixed(2));
      }
    }).draggable({
      drag: function(evt, ui){
        _this.top = Number(ui.position.top.toFixed(2));
        _this.left = Number(ui.position.left.toFixed(2));
      }
    });
  }
}</code></pre>          
        </section> -->

<!--         <section>
          <h2>没有找到正确的 this</h2>
            <pre><code>// 触发滚动条
activeIscroll: function(){
  this.$nextTick(function(){
    if(this.iscroll) {
      this.iscroll.refresh();
    }
    else {
      this.iscroll = new IScroll(this.$els.tabPane, { 
        mouseWheel: true,
        scrollbars: true
      });
    }          
  });
},
addPage: function(){
  this.pages.push(defautPageSetting());
  this.activePageIndex = this.pages.length - 1;
  this.activeIscroll();
}</code></pre>          
        </section> -->

        </section>
        
        <!-- vuex -->
        <section>

          <section>
            <h2>“撤销”、“重做”功能</h2>
            <aside class="notes">
              撤销就是把你的前一步操作取消掉（比如你移动了一个位置，然后不不满意这个移动，你就撤销，恢复原来的位置）
              重做就是把你撤销取消（就是你一把一个图片移动了，感觉不满意，撤销了，然后有感觉其实还不错，然后就重做了）
            </aside>
          </section>

          <section>
            <h2>分析</h2>
            <ul>
              <li>保留每次操作的快照</li>
              <li>撤销就是返回到上一个快照</li>
              <li>重做就是前进到下一个快照</li>
            </ul>
            <aside class="notes">
              每次操作（添加文字元素，改变文字元素）都会改变我们表示整个h5的json, 如果我们把每个操作改变后的json都保留下来，那么其实我们就保存了每个操作的快照，“撤销”功能就是让我们的slide 恢复到上一个操作的json.
            </aside>
          </section>

          <section>
            <h2>问题</h2>
            <ul>
              <li>怎么保存每个操作的快照？（重要）</li>
              <li>怎么返回或者前进一个快照？</li>
            </ul>
          </section>

          <section>
            <h2>问题 - 保存快照</h2>
            <ul>
              <li>使用<code>watch</code>来监测JSON的变化</li>
              <li>使用<code>vuex</code></li>
            </ul>
          </section>
          
          <section>
            <h2>watch 方法</h2>
            <ul>
              <li>拖曳，放大等操作会导致产生大量的快照</li>
              <li>会产生不必要的快照</li>
            </ul>
          </section>

          <section>
            <h2>vuex 插件</h2>
          </section>

          <section>
            <h2>快照的保存</h2>
            <pre><code>// store 
const plugins = [function(store) {
  store.subscribe((mutation, state) => {
    H.hook.$emit('vuex:mutation', state)
  });
}]
// app 初始化
H.hook.$on('vuex:init', function(state) {
  H.slideHistory.push($.extend(true, {}, state));
  H.slideIndex = 0;
});
H.hook.$on('vuex:mutation', function(state) {
  H.slideHistory.push($.extend(true, {}, state));
  H.slideIndex = H.slideHistory.length - 1;
});
H.hook.$on('vuex:replace', function(index) {
  var slide = $.extend(true, {}, H.slideHistory[index])
  _this.$store.replaceState(slide);
});</code></pre>
          </section>

          <section>
            <h2>控制快照的产生</h2>
            <pre><code>$( this.$el ).resizable({
  handles: _this.handles,
  stop: function(evt, ui){
    _this.width = ui.size.width;
    _this.height = ui.size.height;
    _this.$dispatch('postion-change', {
      width: _this.width,
      height: _this.height,
      top: _this.top,
      left: _this.left,
      transform: _this.transform
    }, true);        
  },        
  resize: function(evt, ui){
    _this.width = ui.size.width;
    _this.height = ui.size.height;
    _this.$dispatch('postion-change', {
      width: _this.width,
      height: _this.height,
      top: _this.top,
      left: _this.left,
      transform: _this.transform
    });        
  }
});</code></pre>
          </section>

          <section>
            <h2>快照的后退和前进</h2>
            <pre><code>undo: function(){
  if (H.slideIndex > 0) {
    H.slideIndex -= 1;
    H.hook.$emit('vuex:replace', H.slideIndex);
  }
},
redo: function(){
  if (H.slideIndex < H.slideHistory.length - 1) {
    H.slideIndex += 1;
    H.hook.$emit('vuex:replace', H.slideIndex);
  }
}</code></pre>
          </section>

        </section>
        <!-- 一点人生经验 -->

<section>
  <h2>开发总结</h2>
<ul>
  <li>数据驱动视图</li>
  <li>组件化（业务组件化，功能组件化）</li>
  <li>学会调试（chrome 开发这工具，Vue Devtools, debugger, 打断点）</li>
</ul>
<aside class="notes">
  功能细分，方面大家分工合作，也易于维护，升级
  代码复用
  学会调试（chrome 开发这工具，Vue Devtools, debugger, 打断点）  
</aside>
  
</section>


        <section>
          <h2>一点经验</h2>
          <ul>
            <li>先看文档</li>
            <li>写代码，要从小项目写起</li>
            <li>认真思考</li>
            <li>学会调试</li>
            <li>学会提问</li>
          </ul>
          <aside class="notes">
就是把你问题描述清楚，有人上来就问，那个 xxx 方法怎么不起作用，那个怎么报错了，
我连你用什么框架都不知道，你让别人怎么回答。
1. 你代码的环境（使用什么框架，那个浏览器下）
2. 把你的预期达到的结果和实际的结果描述清楚
3. 把你的代码贴出来，如果不牵扯到构建环节，可以放到在线的 jsbin.com 
4. 如果你的代码比较复杂，请抽象你的代码，往往在这个环境中，你就发现了自己的问题。

          </aside>
        </section>

        <!-- 问与答 -->
        <section>         
          <h2>源码地址</h2>
          <ul>
            <li><a href="http://git.oschina.net/yangjunjun/h5c">http://git.oschina.net/yangjunjun/h5c</a></li>
            <li><a href="http://git.oschina.net/yangjunjun/h5c-server">http://git.oschina.net/yangjunjun/h5c-server</a></li>
            <li><a href="http://git.oschina.net/yangjunjun/h5c-vuex">http://git.oschina.net/yangjunjun/h5c-vuex</a></li>
          </ul>
        </section> 

        <section>
          <h2>我之前写的一些小 demo </h2>
          <p>
            <a href="https://segmentfault.com/a/1190000006724976">https://segmentfault.com/a/1190000006724976</a>
          </p>
        </section>

        <!-- 问与答 -->
        <section>         
          <h2>Q&amp;A</h2>
          <p><small>- EOF -</small></p>
        </section> 
      </div>

    </div>

    <script src="lib/js/head.min.js"></script>
    <script src="js/reveal.js"></script>

    <script>

      // More info https://github.com/hakimel/reveal.js#configuration
      Reveal.initialize({
        controls: true,
        progress: true,
        history: true,
        center: true,

        transition: 'slide', // none/fade/slide/convex/concave/zoom

        // More info https://github.com/hakimel/reveal.js#dependencies
        dependencies: [
          { src: 'lib/js/classList.js', condition: function() { return !document.body.classList; } },
          { src: 'plugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
          { src: 'plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
          { src: 'plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } },
          { src: 'plugin/zoom-js/zoom.js', async: true },
          { src: 'plugin/notes/notes.js', async: true }
        ]
      });

    </script>

  </body>
</html>
